package com.ldy.seal.util;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PrivilegedAction;
import java.security.cert.Certificate;
import java.util.LinkedList;
import java.util.List;

import javax.imageio.ImageIO;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.PrivateKeySignature;
import com.ldy.seal.vo.ISeal;
import com.ldy.seal.vo.KeywordInfo;
import com.ldy.seal.vo.SealInfo;
//import com.sun.image.codec.jpeg.JPEGCodec;
//import com.sun.image.codec.jpeg.JPEGEncodeParam;
//import com.sun.image.codec.jpeg.JPEGImageEncoder;
//import com.sun.pdfview.PDFFile;
//import com.sun.pdfview.PDFPage;

/**
 * PDF工具类
 * 
 * <pre>
 * 当前类签章使用服务端的证书
 * 	1、pdf转图片
 *  2、骑缝章
 *  3、通过位置签章
 *  4、通过关键字签章
 * </pre>
 * 
 * @author lindeyi
 *
 */
@SuppressWarnings("restriction")
public class PDFUtil {

	private static Logger logger = LoggerFactory.getLogger(PDFUtil.class);

	/**
	 * pdf转图片
	 * 
	 * @param srcPdf
	 * @param destDir
	 * @param async
	 * @return int
	 */
	public static int pdf2img(String srcPdf, String destDir, Boolean async) {
		try {
			File dirfile = new File(destDir);
			if (!dirfile.exists()) {
				dirfile.mkdirs();
			}
			PDDocument doc = PDDocument.load(new File(srcPdf));
			PDFRenderer renderer = new PDFRenderer(doc);
			int pageCount = doc.getNumberOfPages();
			Thread thread = new Thread((new Runnable() {
		        @Override
		        public void run() {
		        	try {
		        		for (int i = 1; i <= pageCount; i++) {
							// BufferedImage image = renderer.renderImageWithDPI(i - 1, 200);
							BufferedImage image = renderer.renderImage(i - 1, 2f);
							ImageIO.write(image, "JPG", new File(destDir + File.separator + i + ".jpg"));
						}
						doc.close();
		        	} catch (Exception e) {
						logger.error("", e);
					}
		        	
		        }
		    }));
			thread.start();
			if (!async) {
				// 非异步
				thread.join(30 * 1000);
			}
			return pageCount;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return 0;
	}

//	/**
//	 * TODO pdf转图片 使用pdfview方式，速度快，但生成图片没有印章，不可以，有时间找找问题
//	* @param srcPdf
//	* @param destDir
//	* @return
//	 */
//	public static int pdf2img_(String srcPdf, String destDir) {
//		int countpage = 0;
//		try {
//			File file = new File(srcPdf);
//			RandomAccessFile raf = new RandomAccessFile(file, "r");
//			FileChannel channel = raf.getChannel();
//			MappedByteBuffer buf = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
//			PDFFile pdffile = new PDFFile(buf);
//			// 创建图片文件夹
//			File dirfile = new File(destDir);
//			if (!dirfile.exists()) {
//				dirfile.mkdirs();
//			}
//			// 获得图片页数
//			countpage = pdffile.getNumPages();
//			for (int i = 1; i <= pdffile.getNumPages(); i++) {
//				PDFPage page = pdffile.getPage(i);
//				java.awt.Rectangle rect = new java.awt.Rectangle(0, 0, ((int) page.getBBox().getWidth()),
//						((int) page.getBBox().getHeight()));
//				int n = 2;
//				/** 图片清晰度（n>0且n<7）【pdf放大参数】 */
//				java.awt.Image img = page.getImage(rect.width * n, rect.height * n, rect, /** 放大pdf到n倍，创建图片。 */
//						null, /** null for the ImageObserver */
//						true, /** fill background with white */
//						true /** block until drawing is done */
//				);
//				BufferedImage tag = new BufferedImage(rect.width * n, rect.height * n, BufferedImage.TYPE_INT_RGB);
//				tag.getGraphics().drawImage(img, 0, 0, rect.width * n, rect.height * n, null);
//
//				FileOutputStream out = new FileOutputStream(destDir + "/" + i + ".png");
//				/** 输出到文件流 */
//				JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
//				JPEGEncodeParam param2 = encoder.getDefaultJPEGEncodeParam(tag);
//				param2.setQuality(1f, true);
//				/** 1f~0.01f是提高生成的图片质量 */
//				encoder.setJPEGEncodeParam(param2);
//				encoder.encode(tag);
//				/** JPEG编码 */
//				out.close();
//			}
//			channel.close();
//			raf.close();
//			unmap(buf);
//			/** 如果要在转图片之后删除pdf，就必须要这个关闭流和清空缓冲的方法 */
//		} catch (FileNotFoundException e) {
//			e.printStackTrace();
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
//		return countpage;
//
//	}

	public static void unmap(final Object buffer) {
		AccessController.doPrivileged(new PrivilegedAction<Object>() {
			public Object run() {
				try {
					Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]);
					getCleanerMethod.setAccessible(true);
					sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(buffer, new Object[0]);
					cleaner.clean();
				} catch (Exception e) {
					e.printStackTrace();
				}
				return null;
			}
		});
	}
	
	/**
	 * 插入骑缝章
	 * 
	 * @param inputStream
	 * @param seal
	 * @param y
	 * @return ByteArrayOutputStream
	 */
	public static ByteArrayOutputStream pagingSeal(InputStream inputStream, byte[] seal, Float y) {
		PdfReader reader = null;
		PdfStamper stamper = null;
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		try {
			reader = new PdfReader(inputStream);
			stamper = new PdfStamper(reader, bos);
			Rectangle pageSize = reader.getPageSize(1);
			float width = pageSize.getWidth();
			float height = pageSize.getHeight();
			float yLen = y;
			// 默认右下角
			if (null == y) {
				yLen = height / 2;
			} else {
				if (y > 0 && y < 1) {
					yLen = height * y;
				}
			}

			// pdf的总页数
			int total = reader.getNumberOfPages();
			Image sealImg = Image.getInstance(seal);
			float imgWidth = sealImg.getWidth();
			float w = imgWidth / total; // 单个
			float scale = getScale(width, height, sealImg, width / 5);
			BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(seal));
			for (int i = 1; i <= total; i++) {
				PdfContentByte overContent = stamper.getOverContent(i);
				Image m = cropImage(bufferedImage, i, total);
				m.setAbsolutePosition(width - w * (scale / 100), yLen);
				m.scalePercent(scale);
				overContent.addImage(m);
				overContent.stroke();
			}

			stamper.close();
			reader.close();
			return bos;
		} catch (Exception e) {
			logger.error("", e.getMessage());
			if (null != stamper) {
				try {
					stamper.close();
				} catch (Exception e1) {
				}
			}
			if (null != reader) {
				try {
					reader.close();
				} catch (Exception e1) {
				}
			}
			return null;
		}
	}

	/**
	 * 签章（通过位置）
	 * 
	 * @param inputStream      需要签章的pdf 输入流
	 * @param keyStoreStream   证书输入流
	 * @param keyStorePassword 证书密码
	 * @param signImage        签章图片
	 * @param seals            签章位置
	 * @param reason           签订原因
	 * @param location         签订地址
	 * @throws GeneralSecurityException 
	 * @throws IOException 
	 * @throws DocumentException 
	 * @return ByteArrayOutputStream 
	 */
	public static ByteArrayOutputStream seal(InputStream inputStream, //
			InputStream keyStoreStream, //
			String keyStorePassword, //
			Image signImage, //
			List<SealInfo> seals, //
			String reason, //
			String location) //
			throws GeneralSecurityException, IOException, DocumentException {

		// 读取pdf内容
		PdfReader pdfReader = new PdfReader(inputStream);
		int pageNum = pdfReader.getNumberOfPages();

		// 获取证书相关信息
		// 读取keystore
		KeyStore keyStore = KeyStore.getInstance("PKCS12");
		char[] password = keyStorePassword.toCharArray();
		keyStore.load(keyStoreStream, password);
		//
		String alias = keyStore.aliases().nextElement();
		// 获得私钥
		PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password);
		// 获得证书链
		Certificate[] chain = keyStore.getCertificateChain(alias);

		ByteArrayOutputStream result = null;
		if (null == seals) {
			return null;
		}
		ISeal sealInfo;
		for (int i = 0; i < seals.size(); i++) {
			sealInfo = seals.get(i);
			if (sealInfo.getPage() > pageNum) {
				continue;
			}
			if (i > 0) {
				// 多次签名，得重新读取
				pdfReader = new PdfReader(result.toByteArray());
			}
			// 创建临时字节流 重复使用
			result = new ByteArrayOutputStream();

			// pdf 签章设置
			PdfSignatureAppearance appearance = setPdfSign(pdfReader, result, sealInfo, signImage, reason, location);

			// 签章算法 可以自己实现
			// 摘要算法
			ExternalDigest digest = new BouncyCastleDigest();
			// 签章算法
			ExternalSignature signature = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA1, null);
			// 进行盖章操作 CMS高级电子签名(CAdES)的长效签名规范
			MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, CryptoStandard.CMS);

		}
		pdfReader.close();
		// 写入输出流中
		return result;

	}

	/**
	 * 签章（通过关键字）
	 * 
	 * @param inputStream      需要签章的pdf 输入流
	 * @param keyStoreStream   证书输入流
	 * @param keyStorePassword 证书密码
	 * @param signImage        签章图片
	 * @param signKeyWord      签章关键字
	 * @param reason           签订原因
	 * @param location         签订地址
	 * @throws GeneralSecurityException 
	 * @throws IOException 
	 * @throws DocumentException 
	 * @return ByteArrayOutputStream
	 */
	public static ByteArrayOutputStream seal(InputStream inputStream, //
			InputStream keyStoreStream, //
			String keyStorePassword, //
			Image signImage, //
			String signKeyWord, //
			String reason, //
			String location) //
			throws GeneralSecurityException, IOException, DocumentException {

		// 读取pdf内容
		PdfReader pdfReader = new PdfReader(inputStream);
		// 签章位置对象
		List<KeywordInfo> rectangleCentreList = getSealLocation(pdfReader, signKeyWord);

		if (rectangleCentreList.isEmpty()) {
			return null;
		}

		// 获取证书相关信息
		KeyStore keyStore = KeyStore.getInstance("PKCS12");
		char[] password = keyStorePassword.toCharArray();
		keyStore.load(keyStoreStream, password);
		String alias = keyStore.aliases().nextElement();
		// 获得私钥
		PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password);
		// 获得证书链
		Certificate[] chain = keyStore.getCertificateChain(alias);

		ByteArrayOutputStream result = null;

		ISeal sealInfo;
		for (int i = 0; i < rectangleCentreList.size(); i++) {
			sealInfo = rectangleCentreList.get(i);
			if (i > 0) {
				// 多次签名，得重新读取
				pdfReader = new PdfReader(result.toByteArray());
			}
			// 创建临时字节流 重复使用
			result = new ByteArrayOutputStream();

			// pdf 签章设置
			PdfSignatureAppearance appearance = setPdfSign(pdfReader, result, sealInfo, signImage, reason, location);

			// 签章算法 可以自己实现
			// 摘要算法
			ExternalDigest digest = new BouncyCastleDigest();
			// 签章算法
			ExternalSignature signature = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA1, null);
			// 进行盖章操作 CMS高级电子签名(CAdES)的长效签名规范
			MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, CryptoStandard.CMS);

		}
		// 写入输出流中
		return result;

	}

	/**
	 * pdf签章设置（通用设置）
	 * 
	 * @param pdfReader 
	 * @param result 
	 * @param seal 
	 * @param signImage 
	 * @param reason 
	 * @param location 
	 * @return PdfSignatureAppearance 
	 * @throws DocumentException 
	 * @throws IOException 
	 */
	public static PdfSignatureAppearance setPdfSign(PdfReader pdfReader, ByteArrayOutputStream result, ISeal seal,
			Image signImage, String reason, //
			String location) throws DocumentException, IOException {
		Rectangle pageSize = pdfReader.getPageSize(1);
		float width = pageSize.getWidth();
		float height = pageSize.getHeight();
		float scale = getScale(width, height, signImage, width / 5);
		// 创建签章工具
		PdfStamper pdfStamper = PdfStamper.createSignature(pdfReader, result, '\0', null, true);
		// 获取数字签章属性对象，设定数字签章的属性
		PdfSignatureAppearance appearance = pdfStamper.getSignatureAppearance();
		// 设置签章原因
		appearance.setReason(reason);
		// 设置签章地点
		appearance.setLocation(location);
		// 设置签章图片
		appearance.setSignatureGraphic(signImage);
		// 设置签章级别
		appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
		// 设置签章的显示方式，如下选择的是只显示图章（还有其他的模式，可以图章和签名描述一同显示）
		appearance.setRenderingMode(RenderingMode.GRAPHIC);
		// 设置签章位置 图章左下角x，原点为pdf页面左下角，图章左下角y，图章右上角x，图章右上角y
		appearance.setVisibleSignature(seal.getRectangle(signImage, scale), seal.getPage(), null);
		return appearance;
	}

	/**
	 * 获取签章位置（关键字位置）
	 * 
	 * @param pdfReader 
	 * @param signKeyWord 
	 * @return List<KeywordInfo>
	 * @throws GeneralSecurityException 
	 * @throws IOException 
	 * @throws DocumentException 
	 */
	public static List<KeywordInfo> getSealLocation(PdfReader pdfReader, String signKeyWord) //
			throws GeneralSecurityException, IOException, DocumentException {

		// 读取pdf内容
		int pageNum = pdfReader.getNumberOfPages();
		// 签章位置对象
		List<KeywordInfo> keywordList = new LinkedList<>();

		// 下标从1开始
		for (int page = 1; page <= pageNum; page++) {
			KeywordInfo rectangleCentreBase = new KeywordInfo();
			rectangleCentreBase.setPage(page);
			PdfReaderContentParser pdfReaderContentParser = new PdfReaderContentParser(pdfReader);
			pdfReaderContentParser.processContent(page, new RenderListener() {

				StringBuilder sb = new StringBuilder("");
				int maxLength = signKeyWord.length();

				@Override
				public void renderText(TextRenderInfo textRenderInfo) {
					// 只适用 单字块文档 以及 关键字整个为一个块的情况
					// 设置 关键字文本为单独的块，不然会错位
					boolean isKeywordChunk = textRenderInfo.getText().length() == maxLength;
					if (isKeywordChunk) {
						// 文档按照 块 读取
						sb.delete(0, sb.length());
						sb.append(textRenderInfo.getText());
					} else if (textRenderInfo.getText().length() < maxLength) {
						// 有些文档 单字一个块的情况
						// 拼接字符串
						sb.append(textRenderInfo.getText());
						// 去除首部字符串，使长度等于关键字长度
						if (sb.length() > maxLength) {
							sb.delete(0, sb.length() - maxLength);
						}
					} else if (textRenderInfo.getText().contains(signKeyWord)) {
						sb.delete(0, sb.length());
						sb.append(signKeyWord);
					}
					// 判断是否匹配上
					if (signKeyWord.equals(sb.toString())) {
						KeywordInfo keywordInfo = rectangleCentreBase.copy();

						// 计算中心点坐标

						com.itextpdf.awt.geom.Rectangle2D.Float baseFloat = textRenderInfo.getBaseline()
								.getBoundingRectange();
						com.itextpdf.awt.geom.Rectangle2D.Float ascentFloat = textRenderInfo.getAscentLine()
								.getBoundingRectange();

						float centreX;
						float centreY;
						if (isKeywordChunk) {
							centreX = baseFloat.x + ascentFloat.width / 2;
							centreY = baseFloat.y + ((ascentFloat.y - baseFloat.y) / 2);
						} else if (textRenderInfo.getText().length() < maxLength) {
							centreX = baseFloat.x + ascentFloat.width - (maxLength * ascentFloat.width / 2);
							centreY = baseFloat.y + ((ascentFloat.y - baseFloat.y) / 2);
						} else {
							int length = textRenderInfo.getText().length();
							int index = textRenderInfo.getText().indexOf(signKeyWord);
							centreX = baseFloat.x + ascentFloat.width / length * (index + 1);
							centreY = baseFloat.y + ((ascentFloat.y - baseFloat.y) / 2);
						}

						keywordInfo.setCentreX(centreX);
						keywordInfo.setCentreY(centreY);
						keywordList.add(keywordInfo);
						// 匹配完后 清除
						sb.delete(0, sb.length());
					}
				}

				@Override
				public void renderImage(ImageRenderInfo arg0) {
				}

				@Override
				public void endTextBlock() {
				}

				@Override
				public void beginTextBlock() {
				}
			});
		}
		return keywordList;

	}

	/**
	 * 裁剪图片
	 * 
	 * @param bufferedImage 
	 * @param index 
	 * @param total 
	 * @return Image
	 */
	public static Image cropImage(BufferedImage bufferedImage, int index, int total) {
		int width = bufferedImage.getWidth() / total;
		BufferedImage dest = bufferedImage.getSubimage((index - 1) * width, 0, width, bufferedImage.getHeight());

		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			ImageIO.write(dest, "png", out);
			return Image.getInstance(out.toByteArray());
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 获取最大范围缩放比
	 * 
	 * @param docwidth 
	 * @param docheight 
	 * @param image 
	 * @param maxSize 
	 * @return float
	 */
	public static float getScale(float docwidth, float docheight, Image image, float maxSize) {
		return maxSize / Math.max(image.getWidth(), image.getHeight()) * 100;
	}

}
